//Audio driver. Because the S3 doesn't have a DAC, we use PDM (delta-sigma) output,
//combined with an external analog Sallen-Key lowpass filter. This should actually
//give a better output than the 8-bit DAC in the ESP32.
/*
 * ----------------------------------------------------------------------------
 * "THE BEER-WARE LICENSE" (Revision 42):
 * Jeroen Domburg <jeroen@spritesmods.com> wrote this file. As long as you retain 
 * this notice you can do whatever you want with this stuff. If we meet some day, 
 * and you think this stuff is worth it, you can buy me a beer in return. 
 * ----------------------------------------------------------------------------
 */
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "driver/i2s_pdm.h"
#include "driver/i2s_std.h"
#include <driver/gpio.h>
#include "soc/rtc.h"
#include "soc/rtc_periph.h"
#include "esp_log.h"
#include "sndhw.h"
#include "esp_check.h"

//Implement a lock, as we don't want to run the sound callback while
//the main code is sending commands to it as well.
static SemaphoreHandle_t audio_mux;

#define TAG "audio"

snd_cb_t audio_cb;

void sndhw_lock() {
	xSemaphoreTake(audio_mux, portMAX_DELAY);
}

void sndhw_unlock() {
	xSemaphoreGive(audio_mux);
}

//make this equal to the imf tick rate so the music sounds good
#define SND_CHUNKSZ 560


static i2s_chan_handle_t tx_channel;

void audio_task(void *arg) {
	int16_t snd_in[SND_CHUNKSZ]={0};
	while (1) {
		//Get a chunk of audio data from the source...
		xSemaphoreTake(audio_mux, portMAX_DELAY);
		audio_cb((uint8_t*)snd_in, sizeof(snd_in)/2);
		xSemaphoreGive(audio_mux);
		//send it
		size_t bytes_written;
		ESP_ERROR_CHECK(i2s_channel_write(tx_channel, snd_in, sizeof(snd_in), &bytes_written, portMAX_DELAY));

        // delay maybe?
	}
}


void sndhw_init(int samprate, snd_cb_t cb) {
    /* Setp 1: Determine the I2S channel configuration and allocate TX channel only
     * The default configuration can be generated by the helper macro,
     * it only requires the I2S controller id and I2S role,
     * but note that PDM channel can only be registered on I2S_NUM_0 */
    i2s_chan_config_t tx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);
    tx_chan_cfg.auto_clear = true;
    ESP_ERROR_CHECK(i2s_new_channel(&tx_chan_cfg, &tx_channel, NULL));

    /* Step 2: Setting the configurations of PDM TX mode and initialize the TX channel
     * The slot configuration and clock configuration can be generated by the macros
     * These two helper macros is defined in 'i2s_pdm.h' which can only be used in PDM TX mode.
     * They can help to specify the slot and clock configurations for initialization or re-configuring */
    i2s_pdm_tx_config_t pdm_tx_cfg = {
#if CONFIG_EXAMPLE_PDM_TX_DAC
        .clk_cfg = I2S_PDM_TX_CLK_DAC_DEFAULT_CONFIG(EXAMPLE_PDM_TX_FREQ_HZ),
        /* The data bit-width of PDM mode is fixed to 16 */
        .slot_cfg = I2S_PDM_TX_SLOT_DAC_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO),
#else
        .clk_cfg = I2S_PDM_TX_CLK_DEFAULT_CONFIG(samprate),
        /* The data bit-width of PDM mode is fixed to 16 */
        .slot_cfg = I2S_PDM_TX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO),
#endif
        .gpio_cfg = {
            .clk = 45, // https://www.atomic14.com/2024/01/05/esp32-s3-no-pins.html
            .dout = 38,
            .invert_flags = {
                .clk_inv = false,
            },
        },
    };
    ESP_ERROR_CHECK(i2s_channel_init_pdm_tx_mode(tx_channel, &pdm_tx_cfg));

    /* Step 3: Enable the tx channel before writing data */
    ESP_ERROR_CHECK(i2s_channel_enable(tx_channel));

	audio_mux=xSemaphoreCreateMutex();
	audio_cb=cb;
	xTaskCreatePinnedToCore(&audio_task, "snd", 16*1024, NULL, 3, NULL, 1);
	ESP_LOGI(TAG, "Audio inited.");
}
